import * as config from 'config';
import { Injectable } from '@nestjs/common';
import { AuthDto, CreateTokenDto, EmailCaptchaDto, GraphicsCaptchaDto, VerifyTokenDto } from './dto/auth.dto';
import { MailerService } from '@nestjs-modules/mailer';
import { generateRandomCode } from '@/common/utils/random';
import { dayts } from '@/common/tools/day';
import { IoredisService } from '../ioredis/ioredis.service';
import * as svgCaptcha from 'svg-captcha';
import { JwtService } from '@nestjs/jwt';
import { ExtraContext } from '@/common/interfaces/extracontext.interface';
import { CaptchaTypeEnum } from '@/common/enums';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from '../user/entities/user.entity';
import { Repository } from 'typeorm';
import { RoleService } from '../role/role.service';

@Injectable()
export class AuthService {
  constructor(
    private readonly mailerService: MailerService,
    private readonly ioredisService: IoredisService,
    private readonly roleService: RoleService,
    private readonly jwtService: JwtService,
    @InjectRepository(User) private readonly user_db: Repository<User>
  ) {}

  async sendGraphicsCaptcha(data: GraphicsCaptchaDto, extra: ExtraContext) {
    const { type } = data;
    const { ip } = extra;

    const gcaptcha = svgCaptcha.create({
      size: 4, // 验证码长度
      noise: 2, // 干扰线条的数量
      width: 100,
      height: 40,
      fontSize: 50,
      color: true, // 验证码的字符是否有颜色，默认没有，如果设定了背景，则默认有
      background: 'transparent', // 验证码图片背景颜色
      ignoreChars: '0Oo1LlIiJjTtabPpQqg' // 验证码字符中排除指定容易混淆的字符
    });

    // 将 SVG 字符串转换为 dataURI
    const svgToBase64 = (svgContent: string) => {
      const encodedSvg: string = btoa(
        encodeURIComponent(svgContent).replace(/%([0-9A-F]{2})/g, (match, p1) => {
          return String.fromCharCode(parseInt(p1, 16));
        })
      );
      return `data:image/svg+xml;base64,${encodedSvg}`;
    };

    const captchaText = gcaptcha.text;
    const svgImg = svgToBase64(gcaptcha.data);

    const redisKey = `captcha:${CaptchaTypeEnum.Graphics}:${type}:${ip}`;
    await this.ioredisService.set(redisKey, captchaText, 10 * 60);

    return {
      data: svgImg,
      msg: '图形验证码获取成功'
    };
  }

  async sendEmailCaptcha(data: EmailCaptchaDto, extra: ExtraContext) {
    const { to, type } = data;
    const { ip } = extra;

    const captcha = generateRandomCode(); // 6位数字验证码
    const date = dayts().format('YYYY-MM-DD HH:mm:ss');

    const redisKey = `captcha:${CaptchaTypeEnum.Email}:${type}:${ip}:${to}`;
    await this.ioredisService.set(redisKey, captcha, 10 * 60);

    try {
      await this.mailerService.sendMail({
        to,
        subject: `【${config.get('title')}】收到验证码`,
        // from: config.get('mail.auth.username'), // 已在module中配置过默认发送者
        template: 'mail.template.ejs', // 使用的模板
        context: {
          captcha, // 验证码
          expires: 10, // 过期时间（分钟）
          date, // 日期
          signed: config.get('title')
        }
      });

      return {
        msg: '邮箱验证码发送成功'
      };
    } catch (error) {
      return {
        code: 400,
        errMsg: error.message,
        msg: '邮箱验证码发送失败'
      };
    }
  }

  /**
   * 生成token [非接口]
   * @param {TokenDto} data
   * @returns access_token, refresh_token
   */
  createToken(data: CreateTokenDto) {
    const verify = generateRandomCode(); // token校验码

    const access_token = this.jwtService.sign(
      {
        id: data.id,
        username: data.username,
        roles: data.roles,
        verify,
        access: 1
      },
      { expiresIn: config.get('jwt.accessExpiresIn') }
    );

    const refresh_token = this.jwtService.sign(
      {
        id: data.id,
        verify,
        refresh: 1
      },
      { expiresIn: config.get('jwt.refreshExpiresIn') }
    );

    return {
      access_token,
      refresh_token
    };
  }

  /**
   * token校验 [非接口]
   * @param token
   * @returns 是否有效
   */
  async verifyToken(token: string) {
    try {
      const payload = this.jwtService.verify(token);
      return {
        success: payload
      };
    } catch (error) {
      return {
        success: false,
        errMsg: error.message
      };
    }
  }

  /**
   * 解析token
   * @param token 要解析的token
   */
  parseToken(token: string) {
    const decoded = this.jwtService.decode(token);

    if (decoded) {
      return {
        data: decoded,
        msg: 'token解析成功'
      };
    } else {
      return {
        code: 400,
        data: decoded,
        msg: 'token无效'
      };
    }
  }

  /**
   * 更新token
   * @param {VerifyTokenDto} vtoken {access_token, refresh_token}
   * @returns access_token, refresh_token
   */
  async refreshToken(vtoken: VerifyTokenDto) {
    // access_token 过期了，且 refresh_token 也过期，则需要重新登录获取新的
    const verifyRes = await this.verifyToken(vtoken.refresh_token);
    if (!verifyRes.success) {
      return {
        code: 401,
        errMsg: `refresh_token ${verifyRes.errMsg}`,
        msg: '身份验证过期，请重新登录!'
      };
    }

    // 解析token
    const { data: access_decoded } = this.parseToken(vtoken.access_token);
    const { data: refresh_decoded } = this.parseToken(vtoken.refresh_token);

    // token校验
    if (!access_decoded || !refresh_decoded || access_decoded?.verify !== refresh_decoded?.verify) {
      return {
        code: 401,
        msg: 'token校验失败'
      };
    }

    const oneUser = await this.user_db.findOne({ where: { id: refresh_decoded.id } });

    if (!oneUser) {
      return {
        code: 400,
        msg: '未查询到用户，token更新失败'
      };
    }

    const tokenAttr: CreateTokenDto = {
      id: oneUser.id,
      username: oneUser.username,
      roles: oneUser.roles
    };

    // 同时刷新身份缓存
    await this.identityCache(tokenAttr);

    return {
      ...this.createToken(tokenAttr),
      msg: 'token更新成功'
    };
  }

  /**
   * 身份缓存 [非接口]
   * @param {CreateTokenDto} data
   */
  async identityCache(data: CreateTokenDto) {
    // 根据role获取权限列表
    const { data: permissions } = await this.roleService.findPermissionByRole({ roles: data.roles });

    // 缓存用户的鉴权数据至redis
    const authorityRedisKey = `authority:identity:${data.id}:${data.username}`;
    const ttl = 60 * 30; // 和accessExpiresIn过期时间保持一致
    const identity = { id: data.id, username: data.username, roles: data.roles, permissions };
    await this.ioredisService.set(authorityRedisKey, identity, ttl);
    return identity;
  }

  /**
   * 获取身份信息
   * @param {CreateTokenDto} data
   * @returns 返回 { id, username, roles, permissions }
   */
  async parseIdentity(data: CreateTokenDto) {
    const authorityRedisKey = `authority:identity:${data.id}:${data.username}`;
    let authorityRedisValue = await this.ioredisService.get(authorityRedisKey);

    // 从数据库中重新获取
    if (!authorityRedisValue) {
      authorityRedisValue = await this.identityCache(data);
    }

    return {
      data: authorityRedisValue,
      msg: '获取身份信息成功'
    };
  }
}
